﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CTS.HSG;

namespace Host
{
    class SqlScripterConsoleHost
    {
        private const int Failure = -1;
        private const int Success = 0;

        static int Main(string[] args)
        {
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnCurrentDomainUnhandledException);

            Console.WriteLine(
                "Starting application."
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Starting application."
                );

            StringBuilder sb = new StringBuilder();

            foreach (string arg in args)
            {
                sb.Append(arg);
                sb.Append(" ");
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Arguments to application:{0}",
                sb.ToString()
                );
            
            string server = null;
            string user = null;
            string password = null;

            if (args.Length < 2 || args.Length > 7)
            {
                SqlScripterConsoleHost.PrintUsage();
                return SqlScripterConsoleHost.Failure;
            }

            if (args.Contains("-S", StringComparer.OrdinalIgnoreCase) == false)
            {
                Console.WriteLine(
                    "Specify the SQL Server name."
                    );

                SqlScripterConsoleHost.PrintUsage();
                return SqlScripterConsoleHost.Failure;
            }
            else
            {
                int index = Array.IndexOf(
                    args,
                    "-S"
                    );

                server = args[index + 1];
            }

            if (args.Contains("-U", StringComparer.OrdinalIgnoreCase) == false)
            {
                if (args.Contains("-P", StringComparer.OrdinalIgnoreCase) != false)
                {
                    Console.WriteLine(
                    "Specify the SQL Server user name."
                    );

                    SqlScripterConsoleHost.PrintUsage();
                    return SqlScripterConsoleHost.Failure;
                }
            }
            else
            {
                int index = Array.IndexOf(
                    args,
                    "-U"
                    );

                user = args[index + 1];
            }

            if (args.Contains("-P", StringComparer.OrdinalIgnoreCase) == false)
            {
                if (args.Contains("-U", StringComparer.OrdinalIgnoreCase) != false)
                {
                    Console.WriteLine(
                    "Specify the SQL Server user's password."
                    );

                    SqlScripterConsoleHost.PrintUsage();
                    return SqlScripterConsoleHost.Failure;
                }
            }
            else
            {
                int index = Array.IndexOf(
                    args,
                    "-P"
                    );

                password = args[index + 1];
            }

            string query = null;

            if (args.Contains("-Q", StringComparer.OrdinalIgnoreCase) != false)
            {
                int index = Array.IndexOf(
                    args,
                    "-Q"
                    );

                query = args[index + 1];
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "SQL Server instance:{0}",
                server
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "SQL Server Username:{0}",
                user == null ? "Windows Authenticaton specified." : user
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "SQL Server Password:{0}",
                password == null ? "Windows Authenticaton specified." : "[Password not shown]"
                );

            ScriptEngineConfiguration config = new ScriptEngineConfiguration();
            config.Instance = server;
            config.OptimizerData = true;

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Script optimizer data: {0}",
                config.OptimizerData.ToString()
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Script SQL Server assemblies: {0}",
                config.Assemblies.ToString()
                );

            ScriptEngine engine = new ScriptEngine();
            
            engine.ScriptProgress += (o, e) =>
                {
                    SqlScripterDiagnostics.Log(
                        TraceLevel.Verbose,
                        "Scripting object {0}",
                        e.Current
                        );
                };

            engine.ScriptError += (o, e) =>
                {
                    SqlScripterDiagnostics.Log(
                        TraceLevel.Error,
                        "Error scripting object {0}. Error message: {1}.",
                        e.Current,
                        e.InnerException.Message
                        );
                };

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Retrieving database names from server."
                );

            string[] databases = SqlScripterConsoleHost.GetDatabaseNames(
                server,
                user,
                password,
                query
                );

            sb = new StringBuilder();

            foreach (string database in databases)
            {
                sb.Append(
                    database
                    );

                sb.Append(
                    Environment.NewLine
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "The following databases will be scripted:{0}{1}",
                Environment.NewLine,
                sb.ToString()
                );

            string currentDirectory = Path.GetDirectoryName(
                Assembly.GetExecutingAssembly().Location
                );
            
            string outputDirectory = Path.Combine(
                currentDirectory,
                "output"
                );

            if (Directory.Exists(outputDirectory) == false)
            {
                SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Creating output directory {0}",
                outputDirectory
                );

                Directory.CreateDirectory(
                    outputDirectory
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Scripts will be written to {0}",
                outputDirectory
                );

            Console.WriteLine(
                "Scripts will be written to {0}",
                outputDirectory
                );


            foreach (string database in databases)
            {
                config.Database = database;

                string fileName = string.Format(
                    CultureInfo.InvariantCulture,
                    "{0}_script.sql",
                    database
                    );

                config.FileName = Path.Combine(
                    outputDirectory,
                    fileName
                    );

                Console.WriteLine(
                    "Generating script for database {0}...",
                    database
                    );

                SqlScripterDiagnostics.Log(
                    TraceLevel.Info,
                    "Generating script for database {0}...",
                    database
                    );

                engine.GenerateScript(
                    config
                    );

                SqlScripterDiagnostics.Log(
                    TraceLevel.Verbose,
                    "Script written to {0}.",
                    config.FileName
                    );

                SqlScripterDiagnostics.Log(
                    TraceLevel.Info,
                    "Finished generating script for database {0}.",
                    database
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                "Application exiting."
                );

            return SqlScripterConsoleHost.Success;
        }

        private static string[] GetDatabaseNames(
            string serverName,
            string user,
            string password,
            string filter
            )
        {
            Debug.Assert(
                string.IsNullOrEmpty(serverName) == false,
                "Server name passed to GetDatabaseNames was null or empty!"
                );

            SqlConnectionStringBuilder connectionStringBuilder = new SqlConnectionStringBuilder();
            connectionStringBuilder.DataSource = serverName;
            connectionStringBuilder.InitialCatalog = "master";

            if (string.IsNullOrEmpty(user) != false)
            {
                connectionStringBuilder.IntegratedSecurity = true;
            }
            else
            {
                connectionStringBuilder.IntegratedSecurity = false;
                connectionStringBuilder.UserID = user;
                connectionStringBuilder.Password = password;
            }

            string query = null;

            if (string.IsNullOrEmpty(filter) != false)
            {
                query = @"select name from sys.databases where name not in ('master', 'model', 'msdb', 'tempdb') order by name;";
            }
            else
            {
                query = string.Format(
                    CultureInfo.InvariantCulture,
                    @"select name from sys.databases {0};",
                    filter
                    );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Connection string:{0}",
                connectionStringBuilder.ConnectionString
                );
           
            SqlScripterDiagnostics.Log(
                TraceLevel.Verbose,
                "Executing query:{0}",
                query
                );

            List<string> databases = new List<string>();

            using (SqlConnection connection = new SqlConnection(connectionStringBuilder.ConnectionString))
            {
                using (SqlCommand command = new SqlCommand(query, connection, null))
                {
                    connection.Open();
                    SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);

                    while (reader.Read() != false)
                    {
                        string databaseName = (string)reader["name"];
                        
                        databases.Add(
                            databaseName
                            );
                    }
                }
            }

            return databases.ToArray();
        }

        private static void PrintUsage()
        {
            string usage = string.Format(
                CultureInfo.InvariantCulture,
                "{0}{1}{2}{3}{4}{5}{6}",
                "USAGE: ScriptDB.exe -S [SQL Server Name] -U [username] -P [password] -Q [database query]",
                Environment.NewLine,
                "A single space is required after each switch.",
                Environment.NewLine,
                "Only specify -U and -P if connecting via SQL Server Authentication.",
                Environment.NewLine,
                "-Q allows you to select the databases to script using a partial query. The partial query consists of a 'WHERE name' clause followed by the filter."
                );

            Console.WriteLine(
                usage
                );

            SqlScripterDiagnostics.Log(
                TraceLevel.Info,
                usage
                );
        }

        private static void OnCurrentDomainUnhandledException(
            object sender, 
            UnhandledExceptionEventArgs e
            )
        {
            SqlScripterDiagnostics.Log(
                TraceLevel.Error,
                "Application has encountered an exception."
                );

            Exception exception = e.ExceptionObject as Exception;

            if (e != null)
            {
                SqlScripterDiagnostics.Log(
                TraceLevel.Error,
                "Exception message: {0}",
                exception.Message
                );

                SqlScripterDiagnostics.Log(
                TraceLevel.Error,
                "Stack trace: {0}{1}",
                Environment.NewLine,
                exception.StackTrace
                );
            }

            SqlScripterDiagnostics.Log(
                TraceLevel.Error,
                "Application is terminating due to an exception."
                );

            Console.WriteLine(
                "Application is terminating."
                );
        }


    }
}
